package com.ai.dmc.utils.xml;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
 * 
 * <p>Title: XmlReaderUtil.java</p>
 * <p>Description: 读取xml配置文件的工具类，采用了dom4j对xml的解析方法（使用了XPath，注意引入dom4j.jar和jaxen.jar）。</p>
 * <p>一些相关的dom4j的API介绍如下：</p>
 * <p>
 * <table style="border:1px solid #A4A4A4;" width="100%">
 * 	<tr>
 * 		<td>对象类型</td>
 * 		<td>方法</td>
 * 		<td>说明</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>elementIterator()</td>
 * 		<td>对某节点下的所有子节点进行遍历.
 * 			for(Iterator it=root.elementIterator();it.hasNext();){   
 *                Element element = (Element) it.next();   
 *               // do something   
 *            } 
 *        	</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>element(String nodeName)</td>
 * 		<td>取得某节点的单个子节点.Element memberElm=root.element("member");// "member"是节点名   
 *		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>getText()</td>
 * 		<td>取得节点的文字：memberElm.getText();  ; 
 *		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>elementText(String nodeName)</td>
 * 		<td>取得某节点下的name子节点的文字. String text=memberElm.elementText("name")</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>elements(String nodeName)</td>
 * 		<td>取得某节点下名为"member"的所有子节点并进行遍历.
 * 				List nodes = rootElm.elements("member");   
 * 				for (Iterator it = nodes.iterator(); it.hasNext();) {   
 *					 Element elm = (Element) it.next();   
 *				} 
 * 		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>attribute(String attributeName)</td>
 * 		<td>取得某节点下的某属性
 * 			Attribute attribute=root.element("name").attribute("size");// 属性名size  
 * 			String text=attribute.getText();   //获取属性的文字
 * 			也可以用：
 * 			String text2=root.element("name").attributeValue("size");   
 * 		</td>
 * 	</tr>
 * 	<tr>
 * 		<td>org.dom4j.Element</td>
 * 		<td>root.attributeIterator()</td>
 * 		<td>取得某节点下的所有属性
 * 			Element root=document.getRootElement();       
 *			for(Iterator it=root.attributeIterator();it.hasNext();){   
 *          	Attribute attribute = (Attribute) it.next();   
 *         		String text=attribute.getText();   
 *          	System.out.println(text);   
 *			}   
 * 		</td>
 * 	</tr>
 * </table>
 * </p>
 * <p>Copyright: Copyright (c) 2009</p>
 * <p>Company: 亚信科技（中国）有限公司</p>
 * @author chenhui2
 * @version 1.0 
 *	<p>备注：可以调用Document或Element的asXML()方法将文档或者节点转化为xml字符串</p>
 */
public class XmlReaderUtil {

	private Document doc;
	
	private Element rootNode;

	/**
	 * 构造函数，传入xml文件名或者xml串或xml文件的url，设置xml数据源
	 * @param xml 	xml文件名或者xml串或xml文件的url
	 * @throws Exception
	 */
	public XmlReaderUtil(String xml) throws Exception {
		if (xml == null || xml.trim().length() < 1) {
			throw new Exception("--err------>>>xml file name or xml string is null!");
		}
		if (xml.charAt(0) == '<') { // xml 为xml字符串内容
			doc = DocumentHelper.parseText(xml);
		} else { // xml 为文件名
			SAXReader reader = new SAXReader();
			try {
				doc = reader.read(new File(xml));// 先从相对路径和绝对路径加载
			} catch (Exception e) {
				// 如果文件没找到，则再尝试从类路径装置
				java.net.URL url = Thread.currentThread()
						.getContextClassLoader().getResource(xml);
				if (url == null) { // 类路径也没有找到此文件
					throw e;
				} else {
					doc = reader.read(url.toString());
				}
			}
		}
		doc.normalize();
		
		if (this.doc != null ){
			this.rootNode = this.doc.getRootElement();
		}
		if (this.doc == null || this.rootNode ==null){
			throw new Exception("----err-----xml initialization failed, please check the xml document format");
		}
	}

	
	/**
	 * 
	 * 通过节点的路径取得节点信息，通常是xml文件中只有一个该名称的节点，若有多个默认返回第一个。<br/>
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;host_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;ip_address&gt;10.1.251.121&lt;/ip_address&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;os_user&gt;ftp&lt;/os_user&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;os_password&gt;ftp&lt;/os_password&gt;<br/>
	 * &nbsp;&nbsp;&lt;/host_para&gt;<br/>
	 * .....<br />
	 * 要取得host_para节点，可以用getConfigNode("/config/host_para")取得,
	 * 然后通过得到的Element的elementText("ip_address")方法取得ip_address配置
	 * @param nodePath		节点的路径
	 * @return	Element
	 * @throws Exception
	 * 
	 *
	 */
	public Element getConfigNode(String nodePath) throws Exception {
		// 节点名称为空
		if (nodePath == null || nodePath.trim().length() < 1) {
			throw new Exception("----err----input node path is empty ");
		}
		//取得结果对象，未取得值返回null
		Object result = this.doc.selectSingleNode(nodePath);
		return result==null ? null : (Element)result;
	}
	
	/**
	 * 
	 * 通过节点路径取得所有的节点信息，通常是xml文件中有多个相同名称的节点。<br/>
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;tables_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;11&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;22&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * 	............(省略)<br />
	 * 要取得所有的table节点，可以用getConfigNodeList("/config/tables_para/table")取得,然后对每个节点进行进一步的操作。
	 * @param nodePath	节点的路径
	 * @return	List<Element>		类型为org.dom4j.Element的集合
	 * @throws Exception
	 * 
	 *
	 */
	@SuppressWarnings("unchecked")
	public List<Element> getConfigNodeList(String nodePath) throws Exception{
		// 节点名称为空
		if (nodePath == null || nodePath.trim().length() < 1) {
			throw new Exception("----err----input node, the path is incorrect ");
		}
		//取得结果对象，未取得值返回null
		return this.doc.selectNodes(nodePath);
	}

	/**
	 * 
	 * 通过指定的节点、子节点名称取得指定节点下的所有满足子节点名称条件的节点信息，
	 * 通常是xml文件中某个节点下有多个同名称的子节点。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;tables_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;11&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;22&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * 	............(省略)<br />
	 * 先取得tables_para对应的节点信息，然后通过getConfigNodeList(paraNode, "table")取得tables_para下的所有table节点信息。
	 * @param fixNode		指定节点
	 * @param subNodePath	子节点路径
	 * @return	List<Element>	类型为org.dom4j.Element的集合
	 * @throws Exception	
	 * 
	 *
	 */
	@SuppressWarnings("unchecked")
	public List<Element> getConfigNodeList(Element fixNode, String subNodePath) throws Exception{
		//指定节点为空，或节点名称为空
		if(fixNode==null) {
			return getConfigNodeList(subNodePath);
		}
		if(subNodePath==null || subNodePath.trim().length()<1) {
			return fixNode.elements();
		}else {
			return fixNode.selectNodes(subNodePath);
		}
	}
	
	
	/**
	 * 
	 * 通过指定起始父节点、节点路径、标识这个节点的子节点名称、取值（即确定这个节点的条件）取得节点信息，
	 * 通常是xml文件中有多个该名称的节点，需要通过条件去获取。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;tables_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;11&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;22&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * 	............(省略)<br />
	 * 先取得tables_para对应的节点信息，然后通过getConfigNode(paraNode, "table","table_id","11")取得table_id为11的table节点信息。
	 * 
	 * @param fixNode		指定起始父节点
	 * @param nodePath		要获取的节点的路径
	 * @param subNodeName	要获取的节点的子节点名称
	 * @param subNodeValue	要获取的节点的子节点取值
	 * @return	Element
	 * @throws Exception
	 * 
	 *
	 */
	@SuppressWarnings("unchecked")
	public Element getConfigNode(Element fixNode,String nodePath, String subNodeName, String subNodeValue) throws Exception{
		//节点名称为空
		if(nodePath==null || nodePath.trim().length()<1) {
			throw new Exception("----err----input sub-node path is incorrect ");
		}
		//没有设置属性名称和属性值作为查询条件，默认返回该名称的第一个节点
		if(subNodeName==null || subNodeName.trim().length()<1 || subNodeValue==null || subNodeValue.trim().length()<1) {
			throw new Exception("----err----input sub-node information is incorrect ");
		}
		List<Element> list = null;
		if(fixNode == null) {
			list = this.doc.selectNodes(nodePath);
		}else {
			list = fixNode.selectNodes(nodePath);
		}
		if(list==null || list.size()<1) {
			throw new Exception("----err----not found in the node information");
		}
		for(Element element : list) {
			List<Element> subList = element.elements();
			for (Element subElement : subList) {
				if (subElement != null && subElement.getNodeType() == Element.ELEMENT_NODE
						&& subNodeName.equals(subElement.getName())) {
					//防止属性值为空出错
					if(subNodeValue.equals(subElement.getTextTrim())) {
						return element;
					}
				}
			}
		}
		throw new Exception("----err----does not exist in the attribute name and attribute value of the node");
	}
	
	/**
	 * 
	 * 通过节点路径、标识这个节点的子节点名称、取值（即确定这个节点的条件）取得节点信息，
	 * 通常是xml文件中有多个该名称的节点，需要通过条件去获取。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;tables_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;11&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_id&gt;22&lt;/table_id&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_surfix&gt;_yyyymmdd&lt;/table_surfix&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;source_path&gt;/data/out/2009/08&lt;/source_path&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;file_name&gt;i_cust_200908&lt;/file_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;/table&gt;<br/>
	 * 	............(省略)<br />
	 * 通过getConfigNode("config/tables_para/table","table_id","11")取得table_id为11的table节点信息。
	 * 
	 * @param nodePath		要获取的节点的路径
	 * @param subNodeName	要获取的节点的子节点名称
	 * @param subNodeValue	要获取的节点的子节点取值
	 * @return	Element
	 * @throws Exception
	 * 
	 *
	 */
	public Element getConfigNode(String nodePath, String subNodeName, String subNodeValue) throws Exception{
		return this.getConfigNode(null, nodePath, subNodeName, subNodeValue);
	}
	
	
	/**
	 * 
	 * 通过指定节点，取得子节点的取值。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;host_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;ip_address&gt;10.1.251.121&lt;/ip_address&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;os_user&gt;ftp&lt;/os_user&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;os_password&gt;ftp&lt;/os_password&gt;<br/>
	 * &nbsp;&nbsp;&lt;/host_para&gt;<br/>
	 * .....<br />
	 * 先取得host_para节点，然后用getPropertyValue(paraNode,"ip_address")取得ip_address节点的值"10.1.251.121"
	 * @param fixNode		指定节点
	 * @param subNodeName	子节点名称
	 * @return	String	子节点取值
	 * @throws Exception
	 * 
	 *
	 */
	public String getPropertyValue(Element fixNode, String subNodeName) throws Exception{
		if (fixNode == null) {
			throw new Exception("----err----the specified node is empty");
		}
		return fixNode.elementText(subNodeName);
	}

	/**
	 * 
	 * 通过指定节点，取得该节点下所有指定名称的子节点对应值的集合，针对一个节点有多个同名称的子节点的情况。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;tables_para&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&lt;table&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_name&gt;sys_user&lt;/table_name&gt;<br/>
	 * &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;table_name&gt;sys_menu&lt;/table_name&gt;<br/>
	 * .....<br />
	 * 先取得table节点，然后用getPropertyValueList(paraNode,"table_name")取得table下所有table_name的值的集合，类型为字符串。
	 * 
	 * @param fixNode			指定节点
	 * @param subNodeName		子节点名称
	 * @return	List<String>		子节点取值的集合
	 * @throws Exception
	 * 
	 *
	 */
	@SuppressWarnings("unchecked")
	public List<String> getPropertyValueList(Element fixNode, String subNodeName) throws Exception{
		if (fixNode == null) {
			throw new Exception("----err---the specified node is empty");
		}
		List<String> resultList = new ArrayList<String>();
		List<Element> list = fixNode.elements(subNodeName);
		for(Element e:list) {
			resultList.add(e.getText());
		}
		return resultList;
	}
	/**
	 * 通过节点，取得节点的属性值，如&nbsp;&nbsp;&lt;table  id="123" desc="表描述"&gt;中取得table节点的id值。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;table  id="123" desc="表描述"&gt;<br/>
	 * 取得table节点后，通过getAttrValue(tableNode, "id")能取得table节点的属性id的值"123"。
	 * @param fixNode	节点
	 * @param attrName	属性名称
	 * @return	String
	 * @throws Exception
	 * 
	 *
	 */
	public String getAttrValue(Element fixNode, String attrName) throws Exception {
		if (fixNode == null) {
			throw new Exception("----err----the specified node is empty");
		}
		return fixNode.attributeValue("attrName");
	}
	/**
	 * 
	 * 根据指定节点取得所有的属性信息。
	 * 如下列配置：<br/>
	 * &lt;config&gt;<br/>
	 * &nbsp;&nbsp;&lt;table  id="123" desc="表描述"&gt;<br/>
	 * 取得table节点后，通过getAttributes(tableNode)能取得table节点的所有属性信息。<br/>
	 * 通过遍历集合得到Attribute对象，取得getName()属性名称和getValue()值等信息。
	 * 
	 * @param fixNode		指定节点
	 * @return	List<Attribute>
	 * @throws Exception
	 *
	 */
	@SuppressWarnings("unchecked")
	public List<Attribute> getAttributes(Element fixNode) throws Exception {
		if (fixNode == null) {
			throw new Exception("----err----the specified node is empty");
		}
		return fixNode.attributes();
	}
	
	/**
	 * 
	 * 取得当前的根节点信息
	 * 
	 * @return Element
	 * 
	 */
	public Element getRootNode() {
		return this.rootNode;
	}
	
	/**
	 * 取得当前的org.dom4j.Document对象
	 * @return
	 * Document
	 *
	 */
	public Document getDocument() {
		return this.doc;
	}
}
